﻿/*
 *  RacerLib - XNA Racing Game Level and Tile Management Library
 * 
 *  Copyright (C) 2009 Sebastian Schäfer
 *  
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License 
 *  along with this program; if not, see <http://www.gnu.org/licenses/>.
 */
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using System.IO;
using System.Reflection;
using System.Resources;

using gdv.VCPR.Racer;

namespace RacerEditorWIN
{
    public partial class Editor : Form
    {
        public static int UndoRedoSteps = 10;
        public static String FilterTrack = "Track (.xml)|*.xml|Track old-school (.trk)|*.trk";
        public static String FilterTileSet = "Zipped tileset (.zip)|*.zip|Pure tileset (.xml)|*.xml";

        NewTrackDlg newTrackDlg;

        String trackPath;

        ResourceManager rmTiles;
        Track track;
        TileSet tileSet;

        EMouseMode mouseMode;
        StringRedir stringRedir;
        Tile selectionMoveStart;

        Collection<Track> undo;
        Collection<Track> redo;

        public Editor()
        {
            InitializeComponent();

            init();

            stringRedir = new StringRedir(ref rtbLog);
            Console.SetOut(stringRedir);
        }

        private void init()
        {
            rmTiles = RacerEditorWIN.Tiles.ResourceManager;

            newTrackDlg = new NewTrackDlg();
            trackPath = "";

            initTileSet();

            initTilePicker();
            track = new Track(16, 16);
            initTrack();

            tpTilePicker.Tileset = tileSet;
            tpTrack.Tileset = tileSet;

            mouseMode = EMouseMode.Pointer;
            tpTrack.ShowGrid = true;
            selectionMoveStart = new Tile();
            //tpTrack.ShowTileSelected = true;

            undo = new Collection<Track>();
            redo = new Collection<Track>();
        }

        private Bitmap FetchTile(int type)
        {
            return (Bitmap) rmTiles.GetObject("tile_"+type);
        }

        private void initTileSet()
        {
            tileSet = new TileSet("Street", "Sebastian Schaefer");
            tileSet.Add(41, "Start.png", "Start", FetchTile(41));
            tileSet.Add(42, "Finish.png", "Goal", FetchTile(42));
            tileSet.Add(43, "StartFinish.png", "Start+Goal", FetchTile(43));

            tileSet.Add(44, "Checkpoint.png", "Checkpoint", FetchTile(46));
            tileSet.Add(8, "Road1.png", "Straight", FetchTile(8));
            tileSet.Add(48, "smallCorner.png", "Curve", FetchTile(48));
            tileSet.Add(72, "BigRamp.png", "Ramp", FetchTile(72));
            tileSet.Add(136, "BankedRoadOn.png", "TileLeft", FetchTile(136));
            tileSet.Add(264, "BankedRoadOnRight.png", "TiltRight", FetchTile(264));
            tileSet.Add(520, "BankedRoad.png", "TiltedLeft", FetchTile(520));
            //tileSet.Add(1032, "BankedRoadRight.png", "TiltedRight", FetchTile(1032));
            tileSet.Add(528, "BankedRoadCorner.png", "Tilted Curve", FetchTile(528));
            tileSet.Add(2056, "tunnel.png", "Tunnel", FetchTile(2056));
            tileSet.Add(16416, "Junction.png", "Junction", FetchTile(16416));
        }

        private void initTilePicker()
        {
            Dictionary<int, Bitmap> images = new Dictionary<int, Bitmap>();
            images.Add(41, FetchTile(41));
            images.Add(42, FetchTile(42));
            images.Add(43, FetchTile(43));
            images.Add(46, FetchTile(46));

            images.Add(8, FetchTile(8));
            images.Add(48, FetchTile(48));
            images.Add(72, FetchTile(72));
            images.Add(136, FetchTile(136));
            images.Add(264, FetchTile(264));
            images.Add(520, FetchTile(520));
            images.Add(1032, FetchTile(1032));
            images.Add(528, FetchTile(528));
            images.Add(2056, FetchTile(2056));


            tpTilePicker.SetTileSize(56);
            tpTilePicker.GenerateTiles();
        }

        #region GUI main menu callback
        private void quitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void saveAsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            
            if (DoDialog(sfDlg, "Save track as", FilterTrack) == DialogResult.OK)
            {
                trackPath = sfDlg.FileName;
            }
            if (trackPath != "")
            {
                Track.Save(trackPath, track);
                DoLogLine("Track saved: " + sfDlg.FileName);
            }
        }

        private void tsBtnNew_Click(object sender, EventArgs e)
        {
            bool proceed = true;
            if (track != null)
                if (track.Tiles.Count > 0)
                    proceed = (MessageBox.Show("Really create a new track?","A question", MessageBoxButtons.YesNo) == DialogResult.Yes);
            if (proceed)
                if (newTrackDlg.ShowDialog() == DialogResult.OK)
                {
                    SetNewTrack(new Track(newTrackDlg.TrackWidth, newTrackDlg.TrackHeight));
                    DoLogLine("New Track created: " + newTrackDlg.TrackWidth + "x" + newTrackDlg.TrackHeight);
                }
        }

        private void tsBtnSave_Click(object sender, EventArgs e)
        {
            if (trackPath == "")
                if (DoDialog(sfDlg, "Save track to", FilterTrack) == DialogResult.OK)
                {
                    trackPath = sfDlg.FileName;
                }
            if (trackPath != "")
            {
                Track.Save(trackPath, track);
                DoLogLine("Track saved: " + trackPath);
            }
        }

        private void tsBtnLoad_Click(object sender, EventArgs e)
        {
            bool proceed = true;
            if (track != null)
                proceed = (MessageBox.Show("Really load a new Track?", "A Question", MessageBoxButtons.YesNo) == DialogResult.Yes);
            if (proceed)
                if (DoDialog(ofDlg, "Choose track", FilterTrack) == DialogResult.OK)
                {
                    Track tmpTrack = Track.Load(ofDlg.FileName);
                    if (tmpTrack != null)
                    {
                        trackPath = ofDlg.FileName;
                        SetNewTrack(tmpTrack);
                        DoLogLine("Track loaded: " + ofDlg.FileName + ": " + tmpTrack);
                    }
                }
        }
        #endregion

        #region GUI TilePicker callback
        private void tpTilePicker_MouseClick(object sender, MouseEventArgs e)
        {
            TPPickTile(tpTilePicker.GetFirstTileAt(e));
        }

        #region GUI TilePicker ToolStrip callback
        private void infoToolStripMenuItem_Click(object sender, EventArgs e)
        {
            AboutBox1 ab = new AboutBox1();
            ab.ShowDialog();
        }

        private void tsTPBtnAddTile_Click(object sender, EventArgs e)
        {
            EditTileInfo editTileInfo = new EditTileInfo();
            if (editTileInfo.ShowDialog() == DialogResult.OK)
            {
                tileSet.Tiles.Add(editTileInfo.TileInfo);
                SetNewTileSet(tileSet);
                DoLogLine("New tile added: " + editTileInfo.TileInfo.Name);
            }

        }

        private void tsTPDelTile_Click(object sender, EventArgs e)
        {
            if (tpTilePicker.TileSelected != null)
            {
                int idx = -1;
                for (int i = 0; i < tileSet.Tiles.Count; i++)
                {
                    if (tileSet.Tiles[i].Type == tpTilePicker.TileSelected.Type)
                    {
                        idx = i;
                        break;
                    }
                }
                if (idx >= 0)
                {
                    DoLogLine("Deleted tile: " + tileSet.Tiles[idx].Filename);
                    tileSet.Tiles.RemoveAt(idx);
                    TPDeselectTile();
                    SetNewTileSet(tileSet);
                }
            }
        }

        private void tsTPEditTile_Click(object sender, EventArgs e)
        {
            if (tpTilePicker.TileSelected != null)
            {
                EditTileInfo editTileInfo = new EditTileInfo();
                int idx = -1;
                for (int i = 0; i < tileSet.Tiles.Count; i++)
                {
                    if (tileSet.Tiles[i].Type == tpTilePicker.TileSelected.Type)
                    {
                        idx = i;
                        break;
                    }
                }
                editTileInfo.TileInfo = tileSet.Tiles[idx];
                if (editTileInfo.ShowDialog() == DialogResult.OK)
                {
                    tileSet.Tiles[idx] = editTileInfo.TileInfo;
                    SetNewTileSet(tileSet);
                    DoLog("Edited tile: " + editTileInfo.TileInfo.Name);
                }

            }
        }

        private void loadTileSetToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (DoDialog(ofDlg, "Choose tileset", FilterTileSet) == DialogResult.OK)
            {
                TileSet tileSet = TileSet.Load(ofDlg.FileName);
                if (tileSet != null)
                {
                    SetNewTileSet(tileSet);
                    DoLog("TileSet loaded: " + ofDlg.FileName);
                }
            }
        }

        private void saveTileSetToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (DoDialog(sfDlg, "Save tileset to", FilterTileSet) == DialogResult.OK)
            {
                TileSet.SaveZIP(sfDlg.FileName, tileSet);
                DoLog("TileSet saved: " + sfDlg.FileName);
            }
        }

        private void newTileSetToolStripMenuItem_Click(object sender, EventArgs e)
        {
            tileSet = new TileSet();
            SetNewTileSet(tileSet);
        }

        private void tsBtnUndo_Click(object sender, EventArgs e)
        {
            DoTrackUndo();
        }

        private void tsBtnRedo_Click(object sender, EventArgs e)
        {
            DoTrackRedo();
        }
        #endregion

        #endregion

        #region GUI Track callback
        private void tpTrack_MouseClick(object sender, MouseEventArgs e)
        {
            switch (mouseMode)
            {
                case EMouseMode.Pointer:
                    break;
                case EMouseMode.Add:
                    if (tpTrack.ShowTileMouse)
                    {
                        if (e.Button == MouseButtons.Left)
                            TRPlaceTile();
                        if (e.Button == MouseButtons.Right)
                            RotateTileCW();
                    }
                    break;
                case EMouseMode.Delete:
                    TRDeleteTile();
                    break;
                default:
                    break;
            }
        }

        private void tpTrack_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            /*
            if (tpTrack.ShowTileMouse)
            {
                if (e.Button == MouseButtons.Left)
                    TRDeleteTile();
                if (e.Button == MouseButtons.Right)
                    RotateTileCCW();
            }
             */
        }

        private void tpTrack_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                switch (mouseMode)
                {
                    case EMouseMode.Add:
                        TRPlaceTile();
                        break;
                    case EMouseMode.Delete:
                        TRDeleteTile();
                        break;
                    case EMouseMode.SelectionEnd:
                        TRSelectionEnd(false);
                        break;
                    case EMouseMode.SelectionMove:
                        TRSelectionMoveEnd(false, true);
                        break;
                    case EMouseMode.SelectionPaste:
                        TRSelectionMoveEnd(false, false);
                        break;
                    case EMouseMode.SelectionStart:
                    case EMouseMode.Pointer:
                    default:
                        break;
                }
            }

        }

        private void tpTrack_MouseDown(object sender, MouseEventArgs e)
        {
            switch (mouseMode)
            {
                case EMouseMode.SelectionStart:
                    TRSelectionStart();
                    break;
                case EMouseMode.SelectionMove:
                    TRSelectionMoveStart();
                    break;
                case EMouseMode.SelectionEnd:
                    TRSelectionStart();
                    break;
                case EMouseMode.SelectionPaste:
                    TRSelectionMoveStart();
                    break;
                case EMouseMode.Pointer:
                case EMouseMode.Add:
                case EMouseMode.Delete:
                default:
                    break;
            }
        }

        private void tpTrack_MouseUp(object sender, MouseEventArgs e)
        {
            switch (mouseMode)
            {
                case EMouseMode.SelectionStart:
                    TRSelectionEnd(true);
                    break;
                case EMouseMode.SelectionMove:
                    TRSelectionMoveEnd(true, true);
                    break;
                case EMouseMode.SelectionPaste:
                    TRSelectionMoveEnd(true, false);
                    break;
                case EMouseMode.Pointer:
                case EMouseMode.Add:
                case EMouseMode.Delete:
                case EMouseMode.SelectionEnd:
                default:
                    break;
            }
        }

        #region GUI Track ToolStrip callback
        private void tstrBtnDeselect_Click(object sender, EventArgs e)
        {
            TPDeselectTile();

            UpdateGUIMouseMode(EMouseMode.Pointer);
        }

        private void tstrBtnSelect_Click(object sender, EventArgs e)
        {
            UpdateGUIMouseMode(EMouseMode.Add);
        }

        private void tstrBtnDelete_Click(object sender, EventArgs e)
        {
            TPDeselectTile();

            UpdateGUIMouseMode(EMouseMode.Delete);
        }

        private void tstrBtnSelection_Click(object sender, EventArgs e)
        {
            UpdateGUIMouseMode(EMouseMode.SelectionStart);
        }

        private void tstrBtnSelectionMove_Click(object sender, EventArgs e)
        {
            UpdateGUIMouseMode(EMouseMode.SelectionMove);
        }

        private void tstrBtnRotateCW_Click(object sender, EventArgs e)
        {
            RotateTileCW();
        }

        private void tstrBtnRotateCCW_Click(object sender, EventArgs e)
        {
            RotateTileCCW();
        }

        private void tstrBtnOrientationNorth_Click(object sender, EventArgs e)
        {
            RotateTileTo(ETileOrientation.North);
        }

        private void tstrBtnOrientationEast_Click(object sender, EventArgs e)
        {
            RotateTileTo(ETileOrientation.East);
        }

        private void tstrBtnOrientationSouth_Click(object sender, EventArgs e)
        {
            RotateTileTo(ETileOrientation.South);
        }

        private void tstrBtnOrientationWest_Click(object sender, EventArgs e)
        {
            RotateTileTo(ETileOrientation.West);
        }

        private void tstrBtnTrackResize_Click(object sender, EventArgs e)
        {
            NewTrackDlg trackDlg = new NewTrackDlg()
            {
                TrackWidth = track.Width,
                TrackHeight = track.Height,
                Text = "Set Track Size"
            };
            if (trackDlg.ShowDialog() == DialogResult.OK)
                SetNewTrackSize(trackDlg.TrackWidth, trackDlg.TrackHeight);
        }

        private void tstrBtnTrackCrop_Click(object sender, EventArgs e)
        {
            TRCropToSelection();
        }

        private void tstrBtnSelectionPaste_Click(object sender, EventArgs e)
        {
            UpdateGUIMouseMode(EMouseMode.SelectionPaste);
        }

        private void tstrBtnSelectionDelete_Click(object sender, EventArgs e)
        {
            TRDeleteSelection();
        }
        #endregion
        #endregion

        #region GUI status update function
        private void UpdateGUIMouseMode(EMouseMode MouseMode)
        {
            bool moving = ((MouseMode == EMouseMode.SelectionStart) || (MouseMode == EMouseMode.SelectionEnd));

            tstrBtnDeselect.Checked = MouseMode == EMouseMode.Pointer;
            tstrBtnSelect.Checked = MouseMode == EMouseMode.Add;
            tstrBtnDelete.Checked = MouseMode == EMouseMode.Delete;
            tstrBtnSelection.Checked = moving;
            tstrBtnSelectionMove.Checked = MouseMode == EMouseMode.SelectionMove;
            tstrBtnSelectionPaste.Checked = MouseMode == EMouseMode.SelectionPaste;

            mouseMode = MouseMode;

            tpTrack.ShowTileMouse = (tpTrack.TileMouse.Type != 0) && (mouseMode == EMouseMode.Add);
            tpTrack.ShowTileSelected = moving || (mouseMode == EMouseMode.SelectionMove) || (mouseMode == EMouseMode.SelectionPaste);
            tpTrack.SelectionMoveMode = (mouseMode == EMouseMode.SelectionMove);
            tpTrack.SelectionPasteMode = (mouseMode == EMouseMode.SelectionPaste);

            if (mouseMode == EMouseMode.SelectionStart)
            {
                tpTrack.SelectBoundingBox();
            }

            tpTrack.Refresh();
        }

        private void UpdateTsTRButtonsRotation(ETileOrientation Orientation)
        {
            tstrBtnOrientationNorth.Checked = Orientation == ETileOrientation.North;
            tstrBtnOrientationEast.Checked = Orientation == ETileOrientation.East;
            tstrBtnOrientationSouth.Checked = Orientation == ETileOrientation.South;
            tstrBtnOrientationWest.Checked = Orientation == ETileOrientation.West;
        }

        public void DoLog(String text)
        {
            rtbLog.AppendText(text);
            tsstLabel.Text = text;
        }

        public void DoLogLine(String text)
        {
            DoLog(text + "\n");
        }
        #endregion

        #region Actions
        private void SetNewTrack(Track newTrack)
        {
            track = newTrack;
            pgTrack.SelectedObject = track.TrackInfo;
            tpTrack.Tiles = newTrack.Tiles;

            initTrack();
            tpTrack.Refresh();
        }

        private void SetNewTileSet(TileSet tileSet)
        {
            this.tileSet = tileSet;

            tpTilePicker.Tileset = tileSet;
            tpTrack.Tileset = tileSet;
        }

        private void initTrack()
        {
            tpTrack.SetTileCount(track.Width, track.Height);
        }

        private void TPPickTile(Tile tile)
        {
            if (tile != null)
            {
                tpTilePicker.DoSelectTile(tile);
                tpTilePicker.ShowTileSelected = true;
                tpTilePicker.Refresh();

                tpTrack.ShowTileMouse = true;
                tpTrack.TileMouse.Type = tile.Type;
                UpdateGUIMouseMode(EMouseMode.Add);
                pbCurrentTile.Image = tileSet.GetImage(tile);
            }
            else
            {
                tpTrack.ShowTileMouse = false;
                tpTilePicker.ShowTileSelected = false;
                tpTrack.Refresh();
            }
        }

        private void TPDeselectTile()
        {
            TPPickTile(null);
        }

        private void TRPlaceTile()
        {
            bool doPlace = true;
            int idx = track.ContainsTileAt(tpTrack.TileMouse);
            if (idx >= 0)
            {
                Tile t = track.Tiles[idx];
                if ((t.Type == tpTrack.TileMouse.Type) && (t.Orientation == tpTrack.TileMouse.Orientation))
                    doPlace = false;
            }
            if (doPlace)
            {
                TrackPushUndo("Place Tile");

                track.AddTile(tpTrack.TileMouse);
                tpTrack.Tiles = track.Tiles;
                tpTrack.Refresh();
            }
        }

        private void TRDeleteTile()
        {
            TrackPushUndo("Delete Tile");

            track.RemoveTileAt(tpTrack.TileMouse);
            tpTrack.Tiles = track.Tiles;
            tpTrack.Refresh();
        }

        private void TRSelectionStart()
        {
            if (tpTrack.TileMouse != null)
            {
                tpTrack.DoSelectTile();
                mouseMode = EMouseMode.SelectionEnd;
                tpTrack.Refresh();
            }
        }

        private void TRSelectionEnd(bool endSelecting)
        {
            if (tpTrack.TileMouse != null)
            {
                tpTrack.DoSelectEnd();
                if (endSelecting)
                    mouseMode = EMouseMode.SelectionStart;
                tpTrack.Refresh();
            }
        }

        private void TRSelectionMoveStart()
        {
            if (tpTrack.TileMouse != null)
            {
                selectionMoveStart = tpTrack.TileMouse;
                tpTrack.Refresh();
            }
        }

        private void TRSelectionMoveEnd(bool endMoving, bool moveNotPaste)
        {
            if (tpTrack.TileMouse != null)
            {
                tpTrack.SelectionMoveOffset = new Point(tpTrack.TileMouse.X - selectionMoveStart.X, tpTrack.TileMouse.Y - selectionMoveStart.Y);
                if (endMoving)
                {
                    TRMoveSelectedTiles(-tpTrack.SelectionMoveOffset.X, -tpTrack.SelectionMoveOffset.Y, moveNotPaste);

                    tpTrack.SelectionMoveOffset = new Point();
                    selectionMoveStart = tpTrack.TileMouse;
                }
                tpTrack.Refresh();
            }
        }

        private void TRCropToSelection()
        {
            if ((tpTrack.Selection.Size.Height > 0) | (tpTrack.Selection.Size.Width > 0))
            {
                TRMoveSelectedTiles(tpTrack.Selection.X, tpTrack.Selection.Y, true);
                SetNewTrackSize(tpTrack.Selection.Width + 1, tpTrack.Selection.Height + 1);
            }
        }

        private void TRMoveSelectedTiles(int x, int y, bool moveNotPaste)
        {
            TrackPushUndo("Move Selected Tiles");
            
            Collection<Tile> tiles = new Collection<Tile>();

            foreach (Tile t in tpTrack.TilesSelected)
            {
                if (moveNotPaste)
                    track.RemoveTileAt(t);
                Tile tile = t.Clone();
                tile.Move(x, y);
                tiles.Add(tile);
            }

            foreach(Tile t in tiles)
                track.AddTile(t);

            if (moveNotPaste)
            {
                tpTrack.TileSelectionStart.Move(x, y);
                tpTrack.TileSelectionEnd.Move(x, y);
            }
        }

        private void TRDeleteSelection()
        {
            if ((tpTrack.Selection.Size.Height > 0) | (tpTrack.Selection.Size.Width > 0))
            {
                TrackPushUndo("Deleted Selection");

                foreach (Tile t in tpTrack.TilesSelected)
                {
                    track.RemoveTileAt(t);
                }
            }
        }

        #region Rotation
        private void RotateTileCW()
        {
            tpTrack.TileMouse.Orientation = Tile.RotateOrientationCW(tpTrack.TileMouse.Orientation);
            UpdateTsTRButtonsRotation(tpTrack.TileMouse.Orientation);
            tpTrack.Refresh();
        }

        private void RotateTileCCW()
        {
            tpTrack.TileMouse.Orientation = Tile.RotateOrientationCCW(tpTrack.TileMouse.Orientation);
            UpdateTsTRButtonsRotation(tpTrack.TileMouse.Orientation);
            tpTrack.Refresh();
        }

        private void RotateTileTo(ETileOrientation Orientation)
        {
            while (tpTrack.TileMouse.Orientation != Orientation)
            {
                RotateTileCW();
            }
        }
        #endregion

        private DialogResult DoDialog(FileDialog dlg, String Title, String Filter)
        {
            dlg.Title = Title;
            dlg.Filter = Filter;
            return dlg.ShowDialog();
        }

        private void SetNewTrackSize(int width, int height)
        {
            TrackPushUndo("Changed track-size");

            track.SetSize(width, height);

            SetNewTrack(track);
        }

        private void TrackPushUndo()
        {
            Track t = track.Clone();
            undo.Add(t);
            DoLog("Stored undo element: " + t.Tiles.Count);
            tsBtnUndo.Enabled = true;
            tsBtnRedo.Enabled = false;
            redo.Clear();
        }

        private void TrackPushUndo(String txt)
        {
            Track t = track.Clone();
            undo.Add(t);
            
            //DoLog("Stored undo element: " + t.Tiles.Count + " - " + txt);
            tsBtnUndo.Enabled = true;
            tsBtnRedo.Enabled = false;
            redo.Clear();
        }

        private void DoTrackUndo()
        {
            Track tr = null;
            if (undo.Count > 0)
            {
                redo.Add(track.Clone());
                tr = undo[undo.Count - 1];
                undo.Remove(tr);

                SetNewTrack(tr);
            }

            tsBtnRedo.Enabled = redo.Count > 0;
            tsBtnUndo.Enabled = undo.Count > 0;
        }

        private void DoTrackRedo()
        {
            Track tr = null;
            if (redo.Count > 0)
            {
                undo.Add(track.Clone());
                tr = redo[redo.Count - 1];
                redo.Remove(tr);

                SetNewTrack(tr.Clone());
            }

            tsBtnRedo.Enabled = redo.Count > 0;
            tsBtnUndo.Enabled = undo.Count > 0;
        }
        #endregion
    }
}
